/*
 * PasswordGenerator.java
 *
 * Created on 08 July 2007, 13:37
 *
 * Copyright (C)
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */

package com.grubby.generators;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Main class which generates a set of password files.
 * @author A. HARMEL-LAW. E: andrew.harmel.law@gmail.com
 * @author Rob Newsome
 * @version 0.1
 */
public class PasswordGenerator {
    
    public static final Logger LOGGER
            = Logger.getLogger("com.grubby.generators.PasswordGenerators");
    
    /** Log Strings */
    private static final String NULL_PARAMETER_EX = "A parameter was null: ";
    private static final String NEG_ARG_EX = "A parameter was negative: ";
    private static final String ILLEGAL_ARG_COMBO = "Two arguments don't work"
            + " together: ";
    
    /**
     * Generates a set of files, containing a number of randomly generated
     * passwords.
     * @param genFileLoc The directory to put the generated files in.
     * @param numFiles The number of files to generate.
     * @param numPwdPerFile The number of passwords to generate per file.
     * @param minPwdSize The minimum number of characters in a password.
     * @param maxPwdSize The maximum number of characters in a password.
     * @minNumberNumerals The minimum number of numerals in the password.
     * @throws IllegalArgumentException If the genFileLoc or random objects are
     * null, or any of the int parameters is negative, or the maxPwdSize is less
     * than the minPwdSize or the minNumberNumerals is greater than the
     * maxPwdSize.
     */
    public static void genPasswordFiles(String genFileLoc, int numFiles,
            int numPwdPerFile, int minPwdSize, int maxPwdSize,
            int minNumberNumerals, Random rand) {
        
        if (genFileLoc == null) {
            
            LOGGER.log(Level.WARNING, NULL_PARAMETER_EX + "genFileLoc");
            throw new IllegalArgumentException(NULL_PARAMETER_EX
                    + "genFileLoc");
            
        } else if (rand == null) {
            
            LOGGER.log(Level.WARNING, NULL_PARAMETER_EX + "rand");
            throw new IllegalArgumentException(NULL_PARAMETER_EX + "rand");
            
        } else if (numFiles <= 0) {
            
            LOGGER.log(Level.WARNING, NEG_ARG_EX + numFiles);
            throw new IllegalArgumentException(NEG_ARG_EX + numFiles);
            
        } else if (numPwdPerFile <= 0) {
            
            LOGGER.log(Level.WARNING, NEG_ARG_EX + numPwdPerFile);
            throw new IllegalArgumentException(NEG_ARG_EX + numPwdPerFile);
            
        } else if (minPwdSize <= 0) {
            
            LOGGER.log(Level.WARNING, NEG_ARG_EX + minPwdSize);
            throw new IllegalArgumentException(NEG_ARG_EX + minPwdSize);
            
        } else if (maxPwdSize <= 0) {
            
            LOGGER.log(Level.WARNING, NEG_ARG_EX + maxPwdSize);
            throw new IllegalArgumentException(NEG_ARG_EX + maxPwdSize);
            
        } else if (minNumberNumerals <= 0) {
            
            LOGGER.log(Level.WARNING, NEG_ARG_EX + minNumberNumerals);
            throw new IllegalArgumentException(NEG_ARG_EX + minNumberNumerals);
            
        } else if (minNumberNumerals > maxPwdSize) {
            
            LOGGER.log(Level.WARNING, ILLEGAL_ARG_COMBO + "minNumberNumerals >"
                    + " maxPwdSize");
            throw new IllegalArgumentException(ILLEGAL_ARG_COMBO
                    + "minNumberNumerals > maxPwdSize");
            
        } else {
            
            Writer passwordWriter = null;
            
            try {
                
                for (int i = 0 ; i < numFiles ; i ++) {
                    
                    passwordWriter = new FileWriter(genFileLoc
                            + "passwords_" + (i+1) + ".txt");
                    
                    for (int k = 0 ; k < numPwdPerFile; k ++) {
                        
                        StringBuffer patternBuffer = new StringBuffer();
                        
                        // Calculate password length
                        int pwdLength = RandomGenerators.getRandom(minPwdSize,
                                maxPwdSize);
                        
                        // Get the positions of the numeric characters as a sorted
                        // list
                        List numeralPositions
                                = calcNumeralPositions(minNumberNumerals, pwdLength);
                        
                        // Generate the password pattern
                        for (int m = 0 ; m < pwdLength ; m ++) {
                            
                            // If this is a numeric character
                            if (numeralPositions.size() > 0 &&
                                    m == ((Integer) numeralPositions.get(0))
                                    .intValue()) {
                                // Remove this position from the list
                                numeralPositions.remove(0);
                                // Append the numeral signifier
                                patternBuffer.append('N');
                                
                            } else { // this is a general alphanumeric character
                                
                                // 50-50 chance of this being Alpha (lower or upper
                                // case) or Numeric
                                int myRand = rand.nextInt();
                                
                                if (myRand % 3 == 0) {
                                    
                                    patternBuffer.append('A');
                                    
                                } else if (myRand % 3 == 1) {
                                    
                                    patternBuffer.append('a');
                                    
                                } else {
                                    
                                    patternBuffer.append('N');
                                    
                                }
                            }
                        }
                        
                        patternBuffer.append('\n');
                        String pattern = patternBuffer.toString();
                        passwordWriter.write(RandomGenerators
                                .getStringFromPattern(pattern));
                        
                    }
                    
                    // Close the writer stream
                    passwordWriter.close();
                }
                
            } catch (IOException e) {
                
                LOGGER.log(Level.SEVERE, "An IO Exception was thrown when trying to"
                        + "write to file.");
                throw new RuntimeException("An IO Exception was thrown when trying "
                        + "to write to file.", e);
                
            } finally {
                try {
                    if (passwordWriter != null) {
                        passwordWriter.close();
                    }
                } catch (IOException ex) {
                    Logger.getLogger(PasswordGenerator.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }
    
    /**
     * Generates a set of files, containing a number of randomly generated
     * passwords.
     * @param genFileLoc The directory to put the generated files in.
     * @param numFiles The number of files to generate.
     * @param numPwdPerFile The number of passwords to generate per file.
     * @param minPwdSize The minimum number of characters in a password.
     * @param maxPwdSize The maximum number of characters in a password.
     * @minNumberNumerals The minimum number of numerals in the password.
     * @throws IllegalArgumentException If the genFileLoc or random objects are
     * null, or any of the int parameters is negative, or the maxPwdSize is less
     * than the minPwdSize or the minNumberNumerals is greater than the
     * minPwdSize.
     */
    public static void genPasswordFiles(String genFileLoc, int numFiles,
            int numPwdPerFile, int minPwdSize, int maxPwdSize,
            int minNumberNumerals) {
        
        Random rand = new Random(System.currentTimeMillis());
        genPasswordFiles(genFileLoc, numFiles, numPwdPerFile, minPwdSize,
                maxPwdSize, minNumberNumerals, rand);
        
    }
    
    
    /**
     * @param minNumberNumerals.
     * @param pwdLength.
     * @return The list containing Integer objects representing the positions
     * where the numerals should lie in the password.
     */
    private static List calcNumeralPositions(int minNumberNumerals,
            int pwdLength) {
        
        List<Integer> numeralPositions = new ArrayList<Integer>();
        
        if (minNumberNumerals > pwdLength) {
            minNumberNumerals = pwdLength;
        }
        
        // Calculate positions of numerals
        int positionsAdded = 0;
        while (positionsAdded < minNumberNumerals) {
            
            Integer currentPosition = Integer.valueOf(RandomGenerators.getRandom(0,
                    pwdLength));
            
            if (!numeralPositions.contains(currentPosition)) {
                
                numeralPositions.add(currentPosition);
                positionsAdded ++;
                
            }
        }
        
        // Sort the list in ascending order before returning
        Collections.sort(numeralPositions);
        
        return numeralPositions;
        
    }
}